package com.yytek.modbus.impl;


import com.alibaba.fastjson2.JSON;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.WriteCoilRequest;
import com.serotonin.modbus4j.msg.WriteCoilResponse;
import com.yytek.common.core.constant.CacheConstants;
import com.yytek.common.core.constant.ProtocolConstans;
import com.yytek.common.core.domain.R;
import com.yytek.common.redis.service.RedisService;
import com.yytek.common.core.constant.ValueConstant;
import com.yytek.common.core.domain.AttributeInfo;
import com.yytek.common.core.domain.Device;
import com.yytek.common.core.domain.DeviceConectInfo;
import com.yytek.gw.service.DriverCustomService;
import com.yytek.gw.service.DriverMsgCommonService;
import com.yytek.gw.utils.DriverUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;


import static com.yytek.gw.utils.DriverUtil.attribute;
import static com.yytek.gw.utils.DriverUtil.value;


/**
 * modbus驱动协议
 */
@Slf4j
@Service
public class DriverCustomServiceImpl implements DriverCustomService {

    @Resource
    private RedisService redisService;
    @Value("${server.port}")
    private Integer serverPort;
    @Resource
    private DriverMsgCommonService driverMsgCommonService;
    static ModbusFactory modbusFactory;

    static {
        modbusFactory = new ModbusFactory();
    }

    private volatile Map<String, ModbusMaster> masterMap = new HashMap<>(64);

    @Override
    public void initial() {
    }

    @Override
    public R read(Map<String, AttributeInfo> driverInfo, Map<String, AttributeInfo> dataInfo, Device device) throws Exception {
        ModbusMaster modbusMaster = getMaster(device.getIotId(), driverInfo);
        String msg = readValue(modbusMaster, dataInfo);
        //统一解析发送各个业务处理
        driverMsgCommonService.sendToCommonHandleMsg(device.getIotId(),msg);
        return R.ok(msg);
    }

    @Override
    public R write(Map<String, AttributeInfo> driverInfo, Map<String, AttributeInfo> dataInfo, Device device) throws Exception {
        ModbusMaster modbusMaster = getMaster(device.getIotId(), driverInfo);

        if(writeValue(modbusMaster, dataInfo)){
            //写成功
            return R.ok();
        }else{
            return R.fail();
        }
    }


    /**
     * 获取 Modbus Master
     *
     * @param deviceId   Device Id
     * @param driverInfo Driver Info
     * @return ModbusMaster
     */
    public ModbusMaster getMaster(String deviceId, Map<String, AttributeInfo> driverInfo) throws Exception {
        log.debug("Modbus Tcp Connection Info {}", JSON.toJSONString(driverInfo));
        ModbusMaster modbusMaster = masterMap.get(deviceId);
        //如果是新建连接
        if (null == modbusMaster) {
            IpParameters params = new IpParameters();
            params.setHost(attribute(driverInfo, "host"));
            params.setPort(attribute(driverInfo, "port"));
            modbusMaster = modbusFactory.createTcpMaster(params, true);
            modbusMaster.init();
            //缓存key设备到redis
            String key = CacheConstants.GW_DEVICE_CONECT_KEY+deviceId;
            DeviceConectInfo value = DriverUtil.getConectCacheValue(deviceId,serverPort, ProtocolConstans.MODBUS);
            redisService.setCacheObject(key,value);
            masterMap.put(deviceId, modbusMaster);
        }
        return modbusMaster;
    }

    /**
     * 获取 Value
     *
     * @param modbusMaster ModbusMaster
     * @param dataInfo    dataInfo Info
     * @return String Value
     * @throws ModbusTransportException ModbusTransportException
     * @throws ErrorResponseException   ErrorResponseException
     */
    public String readValue(ModbusMaster modbusMaster, Map<String, AttributeInfo> dataInfo) throws ModbusTransportException, ErrorResponseException {
        int slaveId = attribute(dataInfo, "slaveId");
        int functionCode = attribute(dataInfo, "functionCode");
        int offset = attribute(dataInfo, "offset");
        switch (functionCode) {
            case 1:
                BaseLocator<Boolean> coilLocator = BaseLocator.coilStatus(slaveId, offset);
                Boolean coilValue = modbusMaster.getValue(coilLocator);
                return String.valueOf(coilValue);
            case 2:
                BaseLocator<Boolean> inputLocator = BaseLocator.inputStatus(slaveId, offset);
                Boolean inputStatusValue = modbusMaster.getValue(inputLocator);
                return String.valueOf(inputStatusValue);
            case 3:
                BaseLocator<Number> holdingLocator = BaseLocator.holdingRegister(slaveId, offset, getValueType(dataInfo.get("data").getType()));
                Number holdingValue = modbusMaster.getValue(holdingLocator);
                return String.valueOf(holdingValue);
            case 4:
                BaseLocator<Number> inputRegister = BaseLocator.inputRegister(slaveId, offset, getValueType(dataInfo.get("data").getType()));
                Number inputRegisterValue = modbusMaster.getValue(inputRegister);
                return String.valueOf(inputRegisterValue);
            default:
                return "0";
        }
    }

    /**
     * 写 Value
     *
     * @param modbusMaster ModbusMaster
     * @param dataInfo    dataInfo Info
     * @return Write Result
     * @throws ModbusTransportException ModbusTransportException
     * @throws ErrorResponseException   ErrorResponseException
     */
    public boolean writeValue(ModbusMaster modbusMaster, Map<String, AttributeInfo> dataInfo) throws ModbusTransportException, ErrorResponseException {
        int slaveId = attribute(dataInfo, "slaveId");
        int functionCode = attribute(dataInfo, "functionCode");
        int offset = attribute(dataInfo, "offset");

        switch (functionCode) {
            case 1:
                boolean coilValue = value(dataInfo.get("data").getType(), dataInfo.get("data").getValue());
                WriteCoilRequest coilRequest = new WriteCoilRequest(slaveId, offset, coilValue);
                WriteCoilResponse coilResponse = (WriteCoilResponse) modbusMaster.send(coilRequest);
                return !coilResponse.isException();
            case 3:
                BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, getValueType(dataInfo.get("data").getType()));
                modbusMaster.setValue(locator, value(dataInfo.get("data").getType(), dataInfo.get("data").getValue()));
                return true;
            default:
                return false;
        }
    }

    /**
     * 获取数据类型
     * 说明：此处可根据实际项目情况进行拓展
     * 1.swap 交换
     * 2.大端/小端,默认是大端
     * 3.拓展其他数据类型
     *
     * @param type Value Type
     * @return Modbus Data Type
     */
    public int getValueType(String type) {
        switch (type.toLowerCase()) {
            case ValueConstant.Type.LONG:
                return DataType.FOUR_BYTE_INT_SIGNED;
            case ValueConstant.Type.FLOAT:
                return DataType.FOUR_BYTE_FLOAT;
            case ValueConstant.Type.DOUBLE:
                return DataType.EIGHT_BYTE_FLOAT;
            default:
                return DataType.TWO_BYTE_INT_SIGNED;
        }
    }

}
